/******************************************************************
 * Created by: DO HOANG HAI                                       
 * Date of Creation:           
 * Date of last modification: 1st/Sep/2007
 *                                                                
 * Description: This class provides general functions to interact 
 * with XML data files such as select a single values, select     
 * multiple values, update a value 
 ******************************************************************/

using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Xml.XPath;
using System.IO;
using System.Collections;
using System.Windows.Forms;
namespace Pbdms.DataAccess
{
    /// <summary>
    /// Provide general methods to interact with XML database
    /// each class matches with one xml file
    /// </summary>
    public class XmlHelper
    {
        /// <summary>
        /// name of the file that the class deals with
        /// </summary>
        private String fileName = "";
        private const string XML_PREFIX = "pre";

        /// <summary>
        /// create an instance
        /// </summary>
        /// <param name="fileName"></param>
        public XmlHelper(String fileName)
        {
            this.fileName = fileName;
        }

        /// <summary>
        /// create an instance
        /// </summary>
        /// <param name="fileName">xml file that the object will deal with</param>
        /// <returns>an instance</returns>
        public static XmlHelper create(String fileName)
        {
            return new XmlHelper(fileName);
        }

        /// <summary>
        /// update the XML file which this navigator is refering to
        /// </summary>
        /// <param name="navigator"></param>
        private void updateXMLFile(XPathNavigator navigator)
        {
            navigator.MoveToRoot();
            XmlWriterSettings xs = new XmlWriterSettings();
            xs.Indent = true;
            XmlDocument xdoc = new XmlDocument();
            XmlWriter writer = null;
            try
            {
                writer = XmlWriter.Create(Common.Global.startupPath()+ "/Data/" + fileName,xs);
                navigator.WriteSubtree(writer);
                writer.Close();
            }
            catch
            {
                Common.Global.showMsgOneButton("Couldn't update the database, access is denied!",'W');
            }            
        }

        /// <summary>
        /// get a set of nodes  by XPath given
        /// </summary>
        /// <param name="xmlXPath"></param>
        /// <returns></returns>
        private String fixPrefix(String xmlXPath)
        {
            return xmlXPath.Replace("/", "/" + XML_PREFIX+":");
        }

        /// <summary>
        /// get the XPathNavigator of with given XPath
        /// </summary>
        /// <param name="xmlXPath">XPath to the XML element</param>
        /// <returns>an XPathNavigator</returns>
        public XPathNavigator getNavigator(String xmlXPath)
        {
            try
            { 
                XmlDocument document = new XmlDocument();

                document.Load(Common.Global.startupPath()+ "/Data/" + fileName);
                
                XPathNavigator navigator = document.CreateNavigator();
                XmlNamespaceManager manager = new XmlNamespaceManager(navigator.NameTable);
                manager.AddNamespace(XML_PREFIX, "http://tempuri.org/" + fileName.Substring(0, fileName.Length - 4) + "Schema.xsd");
                string select = fixPrefix(xmlXPath);
                navigator= navigator.SelectSingleNode(select, manager);
             
                
                return navigator;
            }
            catch (Exception ex)
            {
                Pbdms.Common.Global.showMsgOneButton("The file " + fileName + " was not found.", 'W');
                return null;
            }
        }


        /// <summary>
        /// get the list of nodes given by xmlXpath
        /// </summary>
        /// <param name="xmlXPath"></param>
        /// <returns></returns>
        public XPathNodeIterator getNodes(String xmlXPath)
        {
            try
            {
                XmlDocument document = new XmlDocument();
                document.Load(Common.Global.startupPath()+ "/Data/" + fileName);
                XPathNavigator navigator = document.CreateNavigator();
                XmlNamespaceManager manager = new XmlNamespaceManager(navigator.NameTable);
                manager.AddNamespace(XML_PREFIX, "http://tempuri.org/" + fileName.Substring(0, fileName.Length - 4) + "Schema.xsd");
                string select = fixPrefix(xmlXPath);
                XPathNodeIterator nodes = navigator.Select(select, manager);
                if (nodes.Count != 0)
                    return nodes;
                else return null;
            }
            catch (Exception ex)
            {
                Pbdms.Common.Global.showMsgOneButton("The file " + fileName + " was not found.", 'W');                
                return null;
            }
        }

        /// <summary>
        /// get the number of XML elements with given XPath
        /// </summary>
        /// <param name="xmlXpath">XPath to the elements</param>
        /// <returns>number of XML elements</returns>        
        public int getNumberOfNodes(String xmlXPath)
        {
            XPathNodeIterator nodes = getNodes(xmlXPath);
            if (nodes != null)
                return nodes.Count;
            else
                return 0;
        }

        /// <summary>
        /// get inner xml of element given by xmlXPath
        /// </summary>
        /// <param name="xmlXPath">XPath to the element</param>
        /// <returns></returns>
        public String getInnerXML(String xmlXPath)
        {
            XPathNavigator navi = getNavigator(xmlXPath);
            if (navi==null)
            {
                return "";
            }
            else
                return navi.InnerXml;
                
        }
        public String getParentAttribute(String xmlXPath)
        {

            XPathNavigator nav = getNavigator(xmlXPath);
            if (nav==null)
            {
                return null;
            }
            else
            {
                nav.MoveToParent();
                string attrValue = nav.GetAttribute("id",String.Empty);
                return attrValue;
            }
            
        }
        //check whether a node exists
        public string getElement(String xmlXPath)
        {

            XPathNavigator nav = getNavigator(xmlXPath);
            if (nav == null)
            {
                return null;
            }
            else
            {
                //nav.MoveToParent();
                string attrValue = nav.GetAttribute("id", String.Empty);
                return attrValue;
            }

        }
        /// <summary>
        /// get an arraylist of values (by innerXML) of elements selected by XPath
        /// </summary>
        /// <param name="xmlXPath">XPath to select</param>
        /// <returns>an arraylist of Strings</returns>
        public ArrayList selectMultipleElementValues(String xmlXPath)
        {
            XPathNodeIterator nodes = getNodes(xmlXPath);
            if (nodes != null)
            {

                ArrayList result = new ArrayList();
                while (nodes.MoveNext())
                {
                    XPathNavigator navi = nodes.Current;
                    result.Add(navi.InnerXml);
                };
                return result;
            }
            else return null;
 
        }

        /// <summary>
        /// select an arraylist of attributes from a given xPath
        /// </summary>
        /// <param name="xmlXPath"></param>
        /// <param name="attName"></param>
        /// <returns></returns>
        public ArrayList selectMultipleAttributeValues(String xmlXPath, String attName)
        {            
            XPathNodeIterator nodes = getNodes(xmlXPath);
            if (nodes != null)
            {
                ArrayList result = new ArrayList();
                while (nodes.MoveNext())
                {
                    XPathNavigator navi = nodes.Current;
                    result.Add(navi.GetAttribute("id", String.Empty));
                };
                return result;           
            }
            else return null;            
           
        }

        /// <summary>
        /// update element given by XPath with new value. this value must be varified by the schema
        /// </summary>
        /// <param name="xmlXpath">XPath to the element</param>
        /// <param name="newValue">new value for the element</param>
        public void updateTypedElement(String xmlXpath, Object newValue)
        {
            XPathNavigator navigator = getNavigator(xmlXpath);
            navigator.SetTypedValue(newValue);
            updateXMLFile(navigator); 
        }

        /// <summary>
        /// update element given by XPath with new value (as String)
        /// </summary>
        /// <param name="xmlXpath">XPath to the element as a String</param>
        /// <param name="newValue">new value to be assigned</param>
        public void updateUnTypedElement(String xmlXpath, String newValue)
        {
            XPathNavigator navigator = getNavigator(xmlXpath);
            navigator.SetValue(newValue);
            updateXMLFile(navigator);
        }

        /// <summary>
        /// update value of attribute of an element given by XPath String, this value must bevarified by Schema
        /// </summary>
        /// <param name="xmlXpath">XPath String to the contain element</param>
        /// <param name="attName">name of the attribute</param>
        /// <param name="newValue">new value</param>
        public void updateTypedAttribute(String xmlXpath, String attName, Object newValue)
        {          
            XPathNavigator navigator = getNavigator(xmlXpath);
            navigator.MoveToAttribute(attName, String.Empty);
            navigator.SetTypedValue(newValue);
            updateXMLFile(navigator);

        }

        /// <summary>
        /// update value of attribute of an element given by XPath String
        /// </summary>
        /// <param name="xmlXpath">XPath String to the contain element</param>
        /// <param name="attName">name of the attribute</param>
        /// <param name="newValue">new value</param>
        public void updateUnTypedAttribute(String xmlXpath, String attName, String newValue)
        {
            XPathNavigator navigator = getNavigator(xmlXpath);
            navigator.MoveToAttribute(attName, String.Empty);
            navigator.SetValue(newValue);
            updateXMLFile(navigator);
        }

        /// <summary>
        /// insert a new element after the selected element
        /// </summary>
        /// <param name="xmlXPath">XPath String to the selected element</param>
        /// <param name="elementName">name of the new element</param>
        /// <param name="value">value of the element</param>
        public void insertElementAfter(String xmlXPath, String elementName, String value)
        {
            XPathNavigator navigator = getNavigator(xmlXPath);
            navigator.InsertAfter("<"+elementName +">"+ value+"</"+elementName +">");
            updateXMLFile(navigator);
        }

        /// <summary>
        /// insert a new element after the selected element
        /// </summary>
        /// <param name="xmlXPath">XPath String to the selected element</param>
        /// <param name="xmlText">xml text of new element in the form of "<elementName>value</elementName>"</param>
        public void insertElementAfter(String xmlXPath, String xmlText)
        {
            XPathNavigator navigator = getNavigator(xmlXPath);
            navigator.InsertAfter(xmlText);
            updateXMLFile(navigator);
        }

        /// <summary>
        /// append a new child element to an existing element selected using XPath String
        /// </summary>
        /// <param name="xmlXPath">XPath string to the existing element</param>
        /// <param name="elementName">name of new child element</param>
        /// <param name="value">value of the child element</param>
        public void appendChild(String xmlXPath, String elementName, String value)
        {

            XPathNavigator navigator = getNavigator(xmlXPath);
            navigator.AppendChild("<"+elementName +">"+ value+"</"+elementName +">");
            updateXMLFile(navigator);
        }

        /// <summary>
        /// append a new child element (as a string of xml format)
        /// to an existing element selected using XPath String
        /// </summary>
        /// <param name="xmlXPath">XPath string to the selected element element</param>
        /// <param name="xmlText"></param>
        public void appendChild(String xmlXPath, String xmlText)
        {
            XPathNavigator navigator = getNavigator(xmlXPath);
            navigator.AppendChild(xmlText);
            updateXMLFile(navigator);
        }

        /// <summary>
        /// delete the currently selected element
        /// </summary>
        /// <param name="xmlXPath">XPath string to the element</param>
        public void deleteSelf(String xmlXPath)
        {
            XPathNavigator navigator = getNavigator(xmlXPath);    
            navigator.DeleteSelf();
            updateXMLFile(navigator);
          
        }        

        /// <summary>
        /// delete a number of sibling elements with the currently selected element
        /// </summary>
        /// <param name="xmlXPath">XPath to the currently selected element</param>
        /// <param name="from">offset from the current element</param>
        /// <param name="numOfNodesToDelete">number of siblings to delete</param>
        public void deleteRange(String xmlXPath, int from, int numOfNodesToDelete)
        {
            try
            {
                XmlDocument document = new XmlDocument();
                document.Load(Common.Global.startupPath()+ "/Data/" + fileName);
                XPathNavigator navigator = document.CreateNavigator();
                XmlNamespaceManager manager = new XmlNamespaceManager(navigator.NameTable);
                manager.AddNamespace(XML_PREFIX, "http://tempuri.org/" + fileName.Substring(0, fileName.Length - 4) + "Schema.xsd");
                XPathNavigator endNavigator = navigator.SelectSingleNode(fixPrefix(xmlXPath), manager);
                XPathNavigator fromNavigator = navigator.SelectSingleNode(fixPrefix(xmlXPath), manager);

                for (int i = 0; i < from; i++)
                {
                    endNavigator.MoveToNext();
                    fromNavigator.MoveToNext();
                }

                for (int i = 0; i < numOfNodesToDelete; i++)
                {
                    endNavigator.MoveToNext();
                }
                fromNavigator.DeleteRange(endNavigator);
                updateXMLFile(fromNavigator);         
            }
            catch 
            {
                Pbdms.Common.Global.showMsgOneButton("The file " + fileName + " was not found.", 'W');   
            }
 
            
        }

        /// <summary>
        /// delete a number of sibling elements with the currently selected element
        /// from arbitrary siblings to the last sibling
        /// </summary>
        /// <param name="xmlXPath">XPath string to the element</param>
        /// <param name="from">offset from the selected element to the first sibling to delete</param>
        public void deleteRange(String xmlXPath, int from)
        {
            try
            {

                XmlDocument document = new XmlDocument();
                document.Load(Common.Global.startupPath()+ "/Data/" + fileName);
                XPathNavigator navigator = document.CreateNavigator();
                XmlNamespaceManager manager = new XmlNamespaceManager(navigator.NameTable);
                manager.AddNamespace(XML_PREFIX, "http://tempuri.org/" + fileName.Substring(0, fileName.Length - 4) + "Schema.xsd");
                XPathNavigator endNavigator = navigator.SelectSingleNode(fixPrefix(xmlXPath), manager);
                XPathNavigator fromNavigator = navigator.SelectSingleNode(fixPrefix(xmlXPath), manager);
                
                
                //fixed a bug related to no tags 
                //author : Goh Chee Hong
                if (fromNavigator != null)
                {
                    for (int i = 0; i < from; i++)
                    {
                        endNavigator.MoveToNext();
                        fromNavigator.MoveToNext();
                    }

                    while (endNavigator.MoveToNext())
                    {
                    }
                    fromNavigator.DeleteRange(endNavigator);
                    updateXMLFile(fromNavigator);
                }

            }
            catch 
            {
                Pbdms.Common.Global.showMsgOneButton("The file " + fileName + " was not found.", 'W');   
            }
        }
        /// <summary>
        /// get sorted nodes
        /// </summary>
        /// <param name="xmlXPath"></param>
        /// <returns></returns>
        public XPathNodeIterator getSortedNodes(string xmlXPath)
        {
            try
            {              
                XmlDocument document = new XmlDocument();
                document.Load(Common.Global.startupPath() + "/Data/" + fileName);
                XPathNavigator navigator = document.CreateNavigator();
                XmlNamespaceManager manager = new XmlNamespaceManager(navigator.NameTable);
                manager.AddNamespace(XML_PREFIX, "http://tempuri.org/" + fileName.Substring(0, fileName.Length - 4) + "Schema.xsd");
                string select = fixPrefix(xmlXPath);                
                XPathExpression selectExpression = navigator.Compile(select);
                select = "@dateAdded";
                XPathExpression sortExpr = navigator.Compile(select);
                sortExpr.SetContext(manager);
                selectExpression.AddSort(sortExpr, new Common.DateTimeComparer());                
                selectExpression.SetContext(manager);
                XPathNodeIterator nodeIterator  = navigator.Select(selectExpression);
                return nodeIterator;
            }
            catch
            {

            }
            return null;
        }
        public ArrayList selectMultipleSortedElementValues(String xmlXPath, int limitNo)
        {
            XPathNodeIterator nodes = getSortedNodes(xmlXPath);
         
            if (nodes != null)
            {
                ArrayList result = new ArrayList();
                int amountAdded = 0;
                while (nodes.MoveNext())
                {
                    
                    XPathNavigator navi = nodes.Current;
                    //navi.MoveToChild("private", "");
                    //string IsPrivate = navi.InnerXml;
                    string id= navi.GetAttribute("id","");
                    navi.MoveToChild(XPathNodeType.Element);
                    if (navi.InnerXml.Equals("0"))
                    {
                       
                        result.Add(id);
                        amountAdded++;
                        if(amountAdded==limitNo){
                            break;
                        }
                    }
                   
                    
                   
                };
                return result;
            }
            else return null;            
            

        }
    }
}

